home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Nebula 1
/
Nebula One.iso
/
Utilities
/
Workspace
/
Briefcase
/
Source
/
MultDoc.m
< prev
next >
Wrap
Text File
|
1995-06-12
|
14KB
|
544 lines
#import "MultDoc.h"
#import "MultApp.h"
#import <appkit/appkit.h>
@implementation MultDoc: Responder
/* Canon Information Systems is not responsible for anything anyone does with this */
/* code, nor are they responsible for the correctness of this code. Basically, this */
/* has very little to do with the company I work for, and you can't blame them. */
/* This file is best read in a window as wide as this comment, and with tab settings */
/* of 4 spaces and indent setting of 4 spaces (at least, that's what I use). */
/* You are welcome to do as you would with this file under the following conditions. */
/* First, I accept no blame for anything that goes wrong no matter how you use it, */
/* no matter how catastrophic, not even if it stems from a bug in my code. */
/* Second, please keep my notices on it when/if you distribute it. */
/* Third, if you discover any bugs or have any comments, PLEASE TELL ME! Code won't */
/* get better without people picking it apart and giving the writer feedback. */
/* Fourth, if you modify it, please keep a notice that your version is based on mine */
/* in the source files (and keep the notice that mine is based on four other pieces */
/* of code :<). Thanks, and have fun. - Subrata Sircar, ssircar@canon.com */
/* This class should handle all the document-level responsibilities */
/* that a nice document in a multi-doc environment should handle: */
/* creation, deletion, keeping track of dirty bits, not closing w/out */
/* allowing the user to save, etc. Most of this code is lifted from */
/* DrawDocument, or TextLab; heavy use is made of the firstResponder */
/* mechanism. Thanks again, guys. */
/* The default implementation loads windows with scrollviews from a nib file. */
/* To modify, the revert/save/newFromFile methods should be changed to use */
/* the view (subclass) that acts as the content view of the Window. */
/* - Subrata Sircar (ssircar@canon.com) */
/* Version 0.9b Apr-19-92 First Public Release */
/* Version 0.95b Apr-29-92 Multiple Save Types */
/* Version 1.0b Aug-10-92 Minor Bug Fixes */
/* Factory (Class) variables */
static char *default_format = NULL; /* Format string for default title */
static const char *extension = NULL; /* Document extension */
static const int myVersion = 100; /* version number * 100 */
static id zoneList = nil;
static NXCoord ORIGX = 100.0;/* doc starting position and increment */
static NXCoord ORIGY = 100.0;
static int posInc = 20.0;
+ initialize
/* Class variable initialization. DO NOT call from subclasses. */
{
if (self == [MultDoc class]) {
[self setVersion:myVersion];
[self setExtension:LocalString("txt")];
[self setDefault:LocalString("Untitled%d")];
}
return self;
}
/* Extension to use in Workspace file names */
+ setExtension:(const char *)newExtension
{
if (extension) NX_FREE(extension);
extension = NXCopyStringBufferFromZone(newExtension,MyZone);
return self;
}
+ (const char *)extension
{
return extension;
}
/* Default Title string for new documents */
+ setDefault:(const char *)newDefault
{
char temp[MAXPATHLEN+1];
if (default_format) NX_FREE(default_format);
sprintf(temp,"%s.%s",newDefault,[[self class] extension]);
default_format = NXCopyStringBufferFromZone(temp,MyZone);
return self;
}
+ (const char *)default
{
return default_format;
}
/* Factory zone methods. I adopt the virtual zone strategy used by Draw. */
+ (NXZone *)newZone
{
if (!zoneList || ![zoneList count]) {
return NXCreateZone(vm_page_size, vm_page_size, YES);
} else {
return (NXZone *)[zoneList removeLastObject];
}
}
+ (void)reuseZone:(NXZone *)aZone
{
if (!zoneList) zoneList = [List new];
[zoneList addObject:(id)aZone];
NXNameZone(aZone, "Unused");
}
+ allocFromZone:(NXZone *)aZone
{
return [self notImplemented:@selector(allocFromZone:)];
}
+ alloc
{
return [self notImplemented:@selector(alloc)];
}
/* New creation methods */
- setUpNib
{
LoadLocalNib(LocalString("MultDoc.nib"),self,NO,MyZone);
[[view docView] setDelegate:self];
[window makeFirstResponder:[view docView]];
return self;
}
- instanceAwake
{
NXRect frameRect;
calcFrame(printInfo, &frameRect);
if (frameRect.size.width > 1000) frameRect.size.width = 1000;
if (frameRect.size.height > 700) frameRect.size.height = 700;
[window sizeWindow:frameRect.size.width :frameRect.size.height];
[window moveTo:ORIGX :ORIGY];
ORIGX += posInc; ORIGY -= posInc;
if (ORIGX > 700) ORIGX = 110;
if (ORIGY < 100) ORIGY = 160;
[window setDelegate:self];
[self dirty:NO];
[self setSavedDocument:NO];
[self setEmpty:YES];
[self setName:NULL andDirectory:NULL];
[window makeKeyAndOrderFront:self];
return self;
}
+ newFromFile:(const char *)file
{
NXStream *stream;
stream = NXMapFile(file,NX_READONLY);
if (stream) {
self = [self new];
[[view docView] readText:stream];
[[view docView] sizeToFit];
[view display];
[self setName:file];
[self setSavedDocument:YES];
[self setEmpty:NO];
[window makeKeyAndOrderFront:self];
NXCloseMemory(stream,NX_FREEBUFFER);
return self;
} else {
Notify(LocalString("MultDoc: can't open file"),file);
return nil;
}
}
+ new
{
self = [[self class] newFromZone:[[self class] newZone]];
return self;
}
+ newFromZone:(NXZone *)zone;
{
self = [super allocFromZone:zone];
printInfo = [PrintInfo new];
[printInfo setMarginLeft:36.0 right:36.0 top:36.0 bottom:36.0];
[self setUpNib];
[self instanceAwake];
return self;
}
- free
{
NX_FREE(name);
NX_FREE(directory);
[printInfo free];
[window free];
[[self class] reuseZone:MyZone];
return [super free];
}
- view
/*
* Returns the view associated with this document.
*/
{
return view;
}
- (BOOL)needsSaving
/*
* Returns the dirty BOOL associated with this document.
*/
{
return dirty;
}
- (BOOL)isEmpty
/*
* Returns the empty BOOL associated with this document.
*/
{
return empty;
}
- (BOOL)hasSavedDocument
{
return haveSavedDocument;
}
- setEmpty:(BOOL)flag
/*
* Sets the BOOL to flag.
*/
{
empty = flag;
return self;
}
- dirty:(BOOL)flag
/*
* Sets the BOOL to flag.
*/
{
dirty = flag;
[window setDocEdited:flag];
return self;
}
- setSavedDocument:(BOOL)flag
/*
* Sets the BOOL to flag.
*/
{
haveSavedDocument = flag;
return self;
}
- printInfo
{
return printInfo;
}
/* Target/Action methods */
- save:sender
/*
* Saves the file. If this document has never been saved to disk,
* then a SavePanel is put up to ask the user what file name she
* wishes to use to save the document.
*/
{
id del = [NXApp delegate];
id savepanel = [del saveAsPanel:self];
const char *dir = NULL;
if (![self hasSavedDocument]) {
if ([savepanel runModalForDirectory:directory file:name])
[self setName:[savepanel filename]];
else return nil;
}
[self save];
dir = [self directory];
chdir(dir);
[del setDefaultDir:dir];
return self;
}
- saveAs:sender
/* Changes the document name. IF you cancel, it does the right thing */
/* and undoes the changes to the state bits. */
{
BOOL oldSavedFile, oldDirty;
oldDirty = [self needsSaving];
oldSavedFile = [self hasSavedDocument];
[self dirty:YES];
[self setSavedDocument:NO];
if ([self save:sender]) return self;
else {
[self setSavedDocument:oldSavedFile];
[self dirty:oldDirty];
return nil;
}
}
- revertToSaved:sender
/*
* Revert the document back to what is on the disk.
*/
{
MultDoc *oldPtr;
if (![self hasSavedDocument] ||
![self needsSaving] ||
(NXRunAlertPanel(LocalString("Revert"),
LocalString("%s has been edited. Are you sure you want to undo changes?"),
LocalString("Revert"),
LocalString("Cancel"), NULL, name) != NX_ALERTDEFAULT)) {
return self;
}
oldPtr = self;
self = [[oldPtr class] newFromFile:[oldPtr fileName]];
[oldPtr free];
return self;
}
- save
{
NXStream *ts;
int fd;
fd = open([self fileName], O_CREAT | O_WRONLY | O_TRUNC, 0666);
ts = NXOpenFile(fd, NX_WRITEONLY);
if (ts) {
[[view docView] writeText:ts];
NXFlush(ts);
NXClose(ts);
[self dirty:NO];
[self setSavedDocument:YES];
} else Notify(LocalString("MultDoc:save - can't create file"),[self fileName]);
close(fd);
return self;
}
- changeLayout:sender
/*
* Puts up a PageLayout panel and allows the user to pick a different
* size paper to work on. After she does so, the view is resized to the
* new paper size.
* Since the PrintInfo is effectively part of the document, we dirty
* the view (by performing the dirty method).
*/
{
NXRect frame;
if ([[[NXApp delegate] pageLayout:self] runModal] == NX_OKTAG) {
calcFrame(printInfo, &frame);
[window sizeWindow:frame.size.width :frame.size.height];
[self dirty:YES];
[window display];
}
return self;
}
/* Methods related to naming/saving this document. */
- (const char *)fileName
/*
* Gets the fully specified file name of the document.
* If directory is NULL, then the currentDirectory is used.
* If name is NULL, then the default title is used.
*/
{
static char filenamebuf[MAXPATHLEN+1];
if (!directory && !name) [self setName:NULL andDirectory:NULL];
if (directory) {
strcpy(filenamebuf, directory);
strcat(filenamebuf, "/");
} else filenamebuf[0] = '\0';
if (name) strcat(filenamebuf, name);
return filenamebuf;
}
- (const char *)directory
{
return directory;
}
- (const char *)name
{
return name;
}
- setName:(const char *)newName andDirectory:(const char *)newDirectory
/*
* Updates the name and directory of the document.
* newName or newDirectory can be NULL, in which case the name or directory
* will not be changed (unless one is currently not set, in which case
* a default name will be used).
*/
{
char newNameBuf[MAXPATHLEN+1];
static int uniqueCount = 1;
if ((newName && *newName) || !name) {
if (!newName || !*newName) {
sprintf(newNameBuf, [[self class] default], uniqueCount++);
newName = newNameBuf;
} else if (name) NX_FREE(name);
name = NXCopyStringBufferFromZone(newName, MyZone);
}
if ((newDirectory && (*newDirectory == '/')) || !directory) {
if (!newDirectory || (*newDirectory != '/')) newDirectory = [[NXApp delegate] currentDirectory];
else if (directory) NX_FREE(directory);
directory = NXCopyStringBufferFromZone(newDirectory, MyZone);
}
[window setTitleAsFilename:[self fileName]];
NXNameZone(MyZone, [self fileName]);
return self;
}
- setName:(const char *)file
/*
* If file is a full path name, then both the name and directory of the
* document is updated appropriately, otherwise, only the name is changed.
*/
{
char *lastComponent;
char path[MAXPATHLEN+1];
if (file) {
strcpy(path, file);
lastComponent = rindex(path, '/');
if (lastComponent) {
*lastComponent++ = '\0';
return [self setName:lastComponent andDirectory:path];
} else return [self setName:file andDirectory:NULL];
}
return self;
}
/* Window delegate methods. */
- windowWillClose:sender
/*
* If the document has been edited, then this asks the user if she
* wants to save the changes before closing the window. When the window
* is closed, the document itself must be freed. This is accomplished
* via Application's delayedFree: mechanism. Unfortunately, by the time
* delayedFree: frees the document, the window and view instance variables
* will already have automatically been freed by virtue of the window's being
* closed. Thus, those instance variables must be set to nil to avoid their
* being freed twice. (This behavior is set in the window flags in IB.)
*
* Returning nil from this method informs the caller that the window should
* NOT be closed. Anything else implies it should be closed.
*/
{
int save;
if ([self needsSaving] && ![self isEmpty]) {
[window orderFront:self];
save = NXRunAlertPanel(LocalString("Close"),
LocalString("%s has changes. Save them?"),
LocalString("Save"),
LocalString("Don't Save"),
LocalString("Cancel"),
name);
if (save != NX_ALERTDEFAULT && save != NX_ALERTALTERNATE) return nil;
else {
[window endEditingFor:self]; /* terminate any editing */
if (save == NX_ALERTDEFAULT) {
if (![self save:nil]) return nil;
}
}
}
window = nil;
view = nil;
if (printInfo) [NXApp setPrintInfo:nil];
[NXApp delayedFree:self];
chdir([[NXApp delegate] currentDirectory]);
return self;
}
- windowDidBecomeMain:sender
/*
* Switch the Application's PrintInfo to the document's when the document
* window becomes the main window. Also set the cursor appropriately
* depending on which tool is currently selected.
*/
{
[NXApp setPrintInfo:printInfo];
return self;
}
- windowWillMiniaturize:sender toMiniwindow:counterpart
{
char *dot;
char title[MAXPATHLEN+1];
strcpy(title, [self name]);
dot = rindex(title, '.');
if (dot && !strcmp(++dot, extension)) *(--dot) = '\0';
[counterpart setTitle:title];
return self;
}
/* Validates whether a menu command makes sense now */
- (BOOL)validateCommand:menuCell
{
SEL action = [menuCell action];
if (action == @selector(revertToSaved:)) return ([self needsSaving] && [self hasSavedDocument]);
else if (action == @selector(saveAs:)) return (![self isEmpty] && [self hasSavedDocument]);
else if (action == @selector(close:)) return YES;
return YES;
}
/* Text Delegate methods */
- textDidGetKeys:sender isEmpty:(BOOL)flag
{
if (![self needsSaving]) [self dirty:YES];
[self setEmpty:flag];
return self;
}
@end